#include "ivscontrollerbase.h"
#include <QMessageBox>

void onMouse(int event, int x, int y, int flags, void* param);


IVSControllerBase::IVSControllerBase(IVSModel *_ivsModel)
{
    ivsModel = _ivsModel;
    capture = NULL;
    maskROI = NULL;
    isRunning = false;
    isCreatedNamedWindow = false;
    videoPlaySpeedBoost = 0;
    connect(ivsModel, SIGNAL(updateROI(bool)), this, SLOT(updateMaskROI(bool)));
    connect(this, SIGNAL(stateChanged(QString,QColor)), ivsModel, SIGNAL(stateChanged(QString,QColor)));
}

IVSControllerBase::~IVSControllerBase()
{
    release();
}

bool IVSControllerBase::initCapture()
{
    int nTried = 0;
RETRY_GET_CAPTURE:
    switch(ivsModel->CurrentVideoSource().sourceType){
    case IVS::LocalCAM:
        capture = cvCaptureFromCAM(ivsModel->CurrentVideoSource().source.toInt());
        break;

    case IVS::LocalFile:
        capture = cvCaptureFromFile(ivsModel->CurrentVideoSource().Source());
        break;

    case IVS::Stream:
        capture = cvCreateFileCapture(ivsModel->CurrentVideoSource().Source());
        if(capture)
            return true;
        break;

    case IVS::InvalidVideoSource:
        return false;
    }

    if(capture)
    {
        return true;
    }else{
        while(nTried++ < 10){
            goto RETRY_GET_CAPTURE;
        };
        return false;
    }
}

void IVSControllerBase::initCaptureProperty()
{
    fps = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FPS);
    if(fps <= 0 ||fps > 80)
        fps = 32;
    fps += videoPlaySpeedBoost;
    size.width = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_WIDTH);
    size.height = (int) cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT);

}

void IVSControllerBase::createROIMask()
{
    if(maskROI)
        cvReleaseImage(&maskROI);

    maskROI = cvCreateImage(size, IPL_DEPTH_8U, 1);
    cvZero(maskROI);
    if(ivsModel->CurrentVideoSource().ROI.length() <=1 ){
        return;
    }

    CvPoint* pPoint = new CvPoint[ivsModel->CurrentVideoSource().ROI.length()];
    for(int i = 0; i < ivsModel->CurrentVideoSource().ROI.length(); i++)
        pPoint[i] = ivsModel->CurrentVideoSource().ROI[i];
    cvFillConvexPoly(maskROI, pPoint, ivsModel->CurrentVideoSource().ROI.length(), cvScalar(255));
    delete pPoint;
}

void IVSControllerBase::clearROIMask()
{
    if(maskROI){
        cvSet(maskROI, cvScalar(255));
    }
}

void IVSControllerBase::drawROI(IplImage *img, CvScalar color)
{
    if(ivsModel->CurrentVideoSource().ROI.length() > 1){
        for(int i = 1; i < ivsModel->CurrentVideoSource().ROI.length(); i++)
            cvDrawLine(img, ivsModel->CurrentVideoSource().ROI[i-1], ivsModel->CurrentVideoSource().ROI[i], color,2);

        if(!ivsModel->IsMakingROI()){
            cvDrawLine(img, ivsModel->CurrentVideoSource().ROI.last(), ivsModel->CurrentVideoSource().ROI.first(), color,2);
            createROIMask();
        }
    }
}

void IVSControllerBase::showMainImage(IplImage *img, CvScalar lineColor, CvScalar fillColor)
{
    if(ivsModel->VerboseLevel() >= 1)
        drawROI(img, lineColor);
    if(ivsModel->VerboseLevel() >= 2)
        cvAddS(img, fillColor, img, maskROI);

    if(!isCreatedNamedWindow){
        cvNamedWindow(ivsModel->CurrentVideoSource().SourceName(),0);
        isCreatedNamedWindow = false;
    }

    cvShowImage(ivsModel->CurrentVideoSource().SourceName(), img);
}

void IVSControllerBase::showDebugImage(IplImage *img, const char *name)
{
    if(ivsModel->VerboseLevel() >=3)
        cvShowImage(name, img);
    else
        cvDestroyWindow(name);
}

bool IVSControllerBase::initialize()
{    if(initCapture()){
        initCaptureProperty();
        createROIMask();
        return true;
    }else
        return false;
}

void IVSControllerBase::release()
{
    cvReleaseCapture(&capture);
    cvReleaseImage(&maskROI);
    cvDestroyAllWindows();
    isCreatedNamedWindow = false;
}

void IVSControllerBase::setupROI()
{
    cvSetMouseCallback(ivsModel->CurrentVideoSource().SourceName(), ::onMouse, NULL);
    ivsModel->toogleMakingROI();
}

void IVSControllerBase::updateMaskROI(bool isComplete)
{
    if(isComplete)
        createROIMask();
    else if(ivsModel->CurrentVideoSource().ROI.isEmpty())
        clearROIMask();
}

void IVSControllerBase::run(){}
void IVSControllerBase::stop(){}

void IVSControllerBase::findOptimalThreshold(IplImage *img, int noBin, uchar* highTh, uchar* lowTh)
{
    Q_ASSERT(noBin > 1 && noBin <= 256 && img->nChannels == 1);

    int* binHist = new int[noBin + 1];
    int* derivateBinHist = new int[noBin];
    memset(binHist, 0, (noBin + 1)*sizeof(int));
    int binWidth = 256/noBin;
    for(int y = 0; y < img->height; y++){
        uchar* ptr  = (uchar* )(
                    img->imageData + y*img->widthStep
                    );
        for(int x = 0; x <img->width; x++)
            binHist[ptr[x]/binWidth]++;
    }

    if(binHist[0] > binHist[1])
        derivateBinHist[0] = 1;
    else
        derivateBinHist[0] = 0;

    for(int i = 1; i < noBin - 1; i++)
        if(binHist[i] > binHist[i + 1] && binHist[i] > binHist[i - 1])
            derivateBinHist[i] = 1;
        else if(binHist[i] < binHist[i + 1] && binHist[i] < binHist[i - 1])
            derivateBinHist[i] = -1;
        else
            derivateBinHist[i] = 0;
    derivateBinHist[noBin - 1] = 1;

    int theHighestBinIndex, seconHighBinIndex;
    int theLowestBinIndex;

    /* find the highest bin */
    theHighestBinIndex = 0;
    for(int i = 0; i < noBin; i++)
        if(binHist[theHighestBinIndex] < binHist[i])
            theHighestBinIndex = i;


    /* find the second high bin */
    seconHighBinIndex = noBin - 1;
    for(int i = 0; i < noBin; i++)
        if(derivateBinHist[i] == 1 && i != theHighestBinIndex)
            if(binHist[seconHighBinIndex] < binHist[i])
                seconHighBinIndex = i;

    /* find the lowest bin between the highest and the second bin */
    theLowestBinIndex = theHighestBinIndex;
    if(theHighestBinIndex > seconHighBinIndex){
        QMessageBox::warning(NULL,
                             tr("BUZZ BUZZ BUZZ"),
                             tr("Have a problem with the camera. \nNot solve yet."),
                             QMessageBox::Ok
                             );
    }else{
        for(int i = theHighestBinIndex; i < seconHighBinIndex; i++)
            if(derivateBinHist[i] == -1)
                if(binHist[theLowestBinIndex] > binHist[i])
                    theLowestBinIndex = i;
    }
    if(theLowestBinIndex == theHighestBinIndex)
        theLowestBinIndex = (theHighestBinIndex + seconHighBinIndex)/2;
    //    theLowestBinIndex = (theHighestBinIndex + seconHighBinIndex)/2;

    *lowTh = (uchar)((theHighestBinIndex + theLowestBinIndex + 1)*binWidth/2);
    *highTh = (uchar)((theLowestBinIndex + seconHighBinIndex + 1)*binWidth/2);

    if(*lowTh > *highTh)
    {
        uchar tmp = *lowTh;
        *lowTh = *highTh;
        *highTh = tmp;
    }

    delete derivateBinHist;
    delete binHist;
}

void IVSControllerBase::smartThreshold(IplImage *img, int noBin)
{
    IplImage* highThImage = cvCreateImage(cvSize(img->width, img->height),
                                          IPL_DEPTH_8U,
                                          1);
    uchar lowTh, highTh;
    findOptimalThreshold(img, noBin, &highTh, &lowTh);
    cvThreshold(img, highThImage, highTh, 255, CV_THRESH_BINARY);
    cvThreshold(img, img, lowTh, 255, CV_THRESH_BINARY);

    qWarning(qPrintable(QString("h: %1, l: %2").arg(highTh).arg(lowTh)));
    cvErode(highThImage, highThImage, 0, 1);
    cvDilate(highThImage, highThImage,0,12);

    cvAnd(img, highThImage, img);
    cvDilate(img, img, 0, 1);

    cvReleaseImage(&highThImage);
}

int IVSControllerBase::timeInterval()
{
    return baseTime.msecsTo(QTime::currentTime());
}

void IVSControllerBase::updateBaseTime()
{
    baseTime = QTime::currentTime();
}

void IVSControllerBase::applyROIMask(IplImage *img)
{
    if(img->nChannels == 1 && maskROI != NULL){
        cvAnd(img, maskROI, img);
    }
}
